Give linked images SEO-friendly filenames

Clarification
When you link an embedded image through Docly's scaling URL, the last path segment — the "virtual name" — becomes the image's public filename but is ignored when Docly resolves the file. Set it to a descriptive, keyword-bearing slug (commonly the page's own title via encodeURIComponent(Title)) so every generated image is served under an SEO-friendly name instead of a throwaway like image.jpg or photo-10.jpg.
Applies to: Hash templatesImages

What you'll see

You link an embedded image from template code — a card thumbnail in a listing, a logo, a portfolio shot — using Docly's image scaling URL. It renders at the right size, but look at what the image is actually served as:

<img src="#item.Url#/#item.Image1#/500x400x1/image.jpg" alt="photo-10" />

Every generated image lands under the same throwaway name — image.jpg, photo-10.jpg, or a raw attachment id — and the alt text is leftover boilerplate. The page itself ranks, but the pictures carry no descriptive signal of their own. On a site where the images could be pulling in image-search traffic, that is free SEO left on the table.

What's actually happening

Docly serves a scaled copy of an embedded image from a four-segment URL:

<page-url>/<image-field>/<width>x<height>x<mode>/<virtual-name>.jpg

Each segment has a job:

  • <page-url> — the URL of the document or folder that holds the image (for example #item.Url#).
  • <image-field> — the value of the field the image is stored in (for example #item.Image1#). This is the attachment Docly actually reads.
  • <width>x<height>x<mode> — the scaling instruction: target width × height × a scale-mode digit (e.g. 500x400x1 is 500×400 in mode 1, "scale to fill"). See Image scaling for the mode list.
  • <virtual-name>.jpg — the virtual name. This last segment is decorative. The first three segments already identify and scale the image, so Docly does not read this part to find the file — you can put anything here. But it is the filename the browser, the server logs, and search engines see.

That is the leverage point. A file's name is a recognised relevance signal for image search: a descriptive, hyphenated, keyword-bearing filename outranks a generic one for the same picture. image.jpg and photo-10.jpg throw that signal away; a name that describes the picture — ideally the subject of the page it illustrates — captures it. Because the segment is free-form, you get this for nothing: the same image bytes, served under a better name.

What to do

1. Set the virtual name to a descriptive slug. The strongest default is the page's own title, because the image illustrates that page. This is exactly what /tjenester does — each service thumbnail is named after the article it links to:

<img src="#linkImage(f, f.Image1, '128', '128', 1, f.Title + '.jpg')#"
     width="128" height="128" alt="#enc(f.Title)#">

The thumbnails are then served as Systemutvikling.jpg, Altinn-utvikling.jpg, Databaseutvikling.jpg and so on — one descriptive filename per service.

2. Encoding: linkImage does it for you; only the manual path needs encodeURIComponent(). Titles contain spaces and Norwegian characters (æ ø å) that must become a clean, valid path segment — without encoding, a title like Konvertere Access til SQL would break the URL at the first space. When you use linkImage, it handles this itself: it URL-encodes the SEO name and swaps spaces for hyphens, so you pass the raw title. encodeURIComponent is only needed when you still build the four-segment path by hand:

<img src="${encodeURIComponent(f.Url)}/${f.Image1}/128x128x1/${encodeURIComponent(f.Title)}.jpg"
     alt="${f.Title}" />

3. A fixed keyword works too. When a whole listing should target one phrase rather than per-item titles, hard-code the SEO name. This is what /referanser does — the same keyword goes on every variant, so each format is served under the same descriptive filename. A <picture> is only meaningful with more than one format: give it a real AVIF → WebP → JPEG fallback (AVIF <source> first, correct mime types), exactly as in Adopt AVIF and WebP image formats:

<picture>
  <source type="image/avif"
          srcset="#linkImage(item, item.Image1, '500', '400', 1, 'Oslo-systemutvikling.avif')#">
  <source type="image/webp"
          srcset="#linkImage(item, item.Image1, '500', '400', 1, 'Oslo-systemutvikling.webp')#">
  <img src="#linkImage(item, item.Image1, '500', '400', 1, 'Oslo-systemutvikling.jpg')#"
       width="500" height="400" alt="...">
</picture>

Every reference image is then served under the keyword Oslo-systemutvikling.avif, .webp or .jpg depending on what the browser picks. Note that you write this markup yourself, one linkImage URL per variant; Docly does not generate <picture>, <source> or srcset for you.

4. The extension is the format choice, not just a name. The extension on the SEO name now selects the output format: .jpg/.jpeg, .png, .webp and .avif each steer the conversion to that format, while .gif and .svg get no conversion. So …/produkt.webp is served as WebP and …/produkt.avif as AVIF — the same descriptive name, a different format. Pick the extension deliberately; for how to choose (and why AVIF must sit inside a <picture>), see Adopt AVIF and WebP image formats.

5. Fix the alt text while you are there. Image SEO is filename + alt + surrounding text working together. The template defaults you will often inherit — alt="photo-10", alt="profile-07" — say nothing. Set alt to the same descriptive value, e.g. alt="#f.Title#", so the filename and the alt reinforce each other.

6. Prefer linkImage() — it now takes a filename, and the extension sets the format. linkImage() accepts a filename argument, and the extension on that filename decides both the public filename (the SEO name) and the output format. You no longer build the four-segment URL by hand. Two overloads exist:

linkImage(embeddedFileId, w, h [, mode] [, filename])
linkImage(fileObject, embeddedFileId, w, h [, mode] [, filename])

An options object works too:

linkImage(f.Image1, { width: '1200', height: '800', filename: 'produkt.webp' })

The second overload prepends fileObject.Url, so it replaces the #f.Url#/#f.Image1#/… pattern in listings and overviews. linkImage handles the URL-encoding of the SEO name for you and swaps spaces for hyphens — you do not need encodeURIComponent around the name when you use it. For a CSS background image, prefix the returned link with a leading ~ so Docly rewrites it at publish time: style="background-image:url('~#linkImage(p, p.File, '1024', '768', 1, 'virtual-name.jpg')#')". See Linking images and files in hash for the full set of linking forms.

Live examples in production: /tjenester names each thumbnail after its article title, and /referanser uses a fixed keyword across the listing. Getting the page URL itself clean matters for the same reason — see Omit file extensions in links.